package com.zhouzhou.fastjava.http;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class MyHttpRequest {

    private static final Logger logger = LoggerFactory.getLogger(MyHttpRequest.class);
    /**
     * 请求
     */
    private final HttpClient httpClient;
    /**
     *  请求连接
     */
    private final String url;
    /**
     * 请求方式   post ，get  ，put
     */
    private final String method;

    private String contentType;

    private boolean printResText = true;

    private final JSONObject headers = new JSONObject();

    private final JSONObject params = new JSONObject();

    private final List<FilePart> fileParts = Lists.newArrayList();

    private String charset = "utf-8";

    public MyHttpRequest(HttpClient httpClient, String url, String method) {
        this.httpClient = httpClient;
        this.url = url;
        this.method = method;
    }

    public MyHttpRequest param(String name, Object value) {
        this.params.put(name, value);
        return this;
    }

    public MyHttpRequest params(Map<String, Object> params) {
        this.params.putAll(params);
        return this;
    }

    public MyHttpRequest header(String name, Object value) {
        this.headers.put(name, value);
        return this;
    }

    public MyHttpRequest headers(Map<String, Object> headers) {
        this.headers.putAll(headers);
        return this;
    }

    public MyHttpRequest contentType(String contentType) {
        this.contentType = contentType;
        return this;
    }

    public MyHttpRequest jsonContentType() {
        return this.contentType("application/json");
    }

    public MyHttpRequest charset(String charset) {
        this.charset = charset;
        return this;
    }

    public MyHttpRequest part(FilePart filePart) {
        this.fileParts.add(filePart);
        return this;
    }

    public MyHttpRequest printResText(boolean printResText) {
        this.printResText = printResText;
        return this;
    }

    public ResponseWrapper execute() {
        try {
            return doExecute();
        } catch (Exception e) {
            throw new HttpException(e);
        }
    }

    public ResponseWrapper executeUtilReturnNormal() {
        return this.executeUtilReturn(200, 302, 301, 404, 400, 500);
    }

    public ResponseWrapper executeUtilReturn200() {
        return this.executeUtilReturn(200);
    }

    public ResponseWrapper executeUtilReturn(int... codes) {
        Exception throwE = null;
        ResponseWrapper lastResponseWrapper = null;
        for (int i = 0; i < 5; i++) {
            try {
                ResponseWrapper responseWrapper = doExecute();
                int statusCode = responseWrapper.asHttpResponse().getStatusLine().getStatusCode();
                if (Arrays.stream(codes).anyMatch(r -> Objects.equals(r, statusCode))) {
                    return responseWrapper;
                }
                logger.warn("{}, 接口返回{}，期望返回{}，即将重试", url, statusCode, JSON.toJSONString(codes));
                lastResponseWrapper = responseWrapper;
            } catch (Exception e) {
                // continue
                logger.warn("{}, 接口异常，即将重试，{}", url, e.getMessage());
                throwE = e;
            }
        }
        if (lastResponseWrapper != null) {
            return lastResponseWrapper;
        } else {
            throw new HttpException(throwE);
        }
    }

    public ResponseWrapper executeUtilReturnAndWaitMillSecond(long millSecond, int... codes) {
        Exception throwE = null;
        ResponseWrapper lastResponseWrapper = null;
        for (int i = 0; i < 5; i++) {
            try {
                ResponseWrapper responseWrapper = doExecute();
                int statusCode = responseWrapper.asHttpResponse().getStatusLine().getStatusCode();
                if (Arrays.stream(codes).anyMatch(r -> Objects.equals(r, statusCode))) {
                    return responseWrapper;
                }
                logger.warn("{}, 接口返回{}，期望返回{}，即将重试", url, statusCode, JSON.toJSONString(codes));
                lastResponseWrapper = responseWrapper;
            } catch (Exception e) {
                // continue
                logger.warn("{}, 接口异常，即将重试，{}", url, e.getMessage());
                throwE = e;
            }
            if (millSecond > 0) {
                try {
                    Thread.sleep(millSecond);
                } catch (InterruptedException e) {
                }
            }
        }
        if (lastResponseWrapper != null) {
            return lastResponseWrapper;
        } else {
            throw new HttpException(throwE);
        }
    }

    private ResponseWrapper doExecute() throws Exception {
        switch (method) {
            case "GET":
                return doGet();
            case "POST":
                return doPost();
            case "PUT":
                return doPut();
            default:
                throw new HttpException("暂不支持" + method);
        }
    }

    private ResponseWrapper doPost() throws Exception {
        // 存在文件上传
        if (fileParts.size() > 0) {
            return doPostByMultiPart();
        }

        // json 格式
        if (contentType != null && contentType.startsWith("application/json")) {
            return doPostByJson();
        }
        // form-data
        if (contentType != null && contentType.startsWith("multipart/form-data")) {
            return doPostByFormdata();
        }



        // form 格式
        return doPostByForm();
    }



    //暂时只支持application/json方法
    private ResponseWrapper doPut() throws Exception {
//        // 存在文件上传
//        if (fileParts.size() > 0) {
//            return doPostByMultiPart();
//        }

        // json 格式
//        if (contentType != null && contentType.startsWith("application/json")) {
//        }
        return doPutByJson();

//        // form 格式
//        return doPostByForm();
    }

    private ResponseWrapper doPostByForm() throws Exception {
        HttpPost httpPost = new HttpPost(url);
        List<NameValuePair> pairs = Lists.newArrayList();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String entryValue;
            if (entry.getValue() == null) {
                entryValue = StringUtils.EMPTY;
            } else {
                entryValue = entry.getValue().toString();
            }
            pairs.add(new BasicNameValuePair(entry.getKey(), entryValue));
        }
        UrlEncodedFormEntity httpEntity = new UrlEncodedFormEntity(pairs, charset);
        httpPost.setEntity(httpEntity);
        return new ResponseWrapper(httpPost, doExecuteRequest(httpPost), this);
    }

    private ResponseWrapper doPostByJson() throws Exception {
        HttpPost httpPost = new HttpPost(url);
        StringEntity stringEntity = new StringEntity(params.toJSONString(), charset);
        stringEntity.setContentType(contentType);
        httpPost.setEntity(stringEntity);
        return new ResponseWrapper(httpPost, doExecuteRequest(httpPost), this);
    }

    private ResponseWrapper doPostByFormdata() throws Exception {
        MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();

//        entityBuilder.addBinaryBody("img",file, ContentType.MULTIPART_FORM_DATA,fileName);
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String entryValue;
            if (entry.getValue() == null) {
                entryValue = StringUtils.EMPTY;
            } else {
                entryValue = entry.getValue().toString();
            }
            entityBuilder.addTextBody(entry.getKey(), entryValue);
        }


        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(entityBuilder.build());
        return new ResponseWrapper(httpPost, doExecuteRequest(httpPost), this);
    }


    private ResponseWrapper doPutByJson() throws Exception {
        HttpPut httpPut = new HttpPut(url);
        StringEntity stringEntity = new StringEntity(params.toJSONString(), charset);
        stringEntity.setContentType(contentType);
        httpPut.setEntity(stringEntity);
        return new ResponseWrapper(httpPut, doExecuteRequest(httpPut), this);
    }

    private ResponseWrapper doPostByMultiPart() throws Exception {
        MultipartEntityBuilder entityBuilder = MultipartEntityBuilder
                .create();
        ContentType textContentType = ContentType.TEXT_PLAIN.withCharset(charset);
        for (String key : params.keySet()) {
            entityBuilder.addTextBody(key, params.getString(key), textContentType);
        }
        for (FilePart filePart : fileParts) {
            entityBuilder.addBinaryBody(filePart.getName(), filePart.getFile(), ContentType.create(filePart.getContentType()), filePart.getFilename());
        }
        HttpPost httpPost = new HttpPost(url);
        entityBuilder.setCharset(Charset.forName(charset));
        httpPost.setEntity(entityBuilder.build());
        return new ResponseWrapper(httpPost, doExecuteRequest(httpPost), this);
    }

    private MyHttpResponse doExecuteRequest(HttpUriRequest httpUriRequest) throws Exception {
        try {
            for (String key : headers.keySet()) {
                httpUriRequest.setHeader(key, headers.getString(key));
            }
            HttpResponse httpResponse = httpClient.execute(httpUriRequest);
            MyHttpResponse myHttpResponse = new MyHttpResponse(httpResponse);
            if (this.printResText) {
                logger.info("\n请求: {} {}\n返回码: {}\n参数: {}\n返回结果: {}",
                        method,
                        url,
                        httpResponse.getStatusLine().getStatusCode(),
                        JSON.toJSONString(params),
                        new String(myHttpResponse.getResBytes(), charset)
                );
            } else {
                logger.info("\n请求: {} {}\n返回码: {}\n参数: {}\n返回结果(输出前200个字符): {}",
                        method,
                        url,
                        httpResponse.getStatusLine().getStatusCode(),
                        JSON.toJSONString(params),
                        StringUtils.left(new String(myHttpResponse.getResBytes(), charset), 200)
                );
            }
            myHttpResponse.close();
            return myHttpResponse;
        } catch (Exception e) {
            onExecuteError(e);
            throw e;
        }
    }

    private void onExecuteError(Exception e) {
        logger.error("请求: {} {}\n参数: {}",
                method,
                url,
                JSON.toJSONString(params),
                e
        );
    }

    public String getCharset() {
        return charset;
    }

    private ResponseWrapper doGet() throws Exception {
        URIBuilder uriBuilder = new URIBuilder(url);
        for (String key : params.keySet()) {
            uriBuilder.addParameter(key, params.getString(key));
        }
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        return new ResponseWrapper(httpGet, doExecuteRequest(httpGet), this);
    }

    public static MyHttpRequest get(HttpClient httpClient, String url) {
        return new MyHttpRequest(httpClient, url, "GET");
    }

    public static MyHttpRequest post(HttpClient httpClient, String url) {
        return new MyHttpRequest(httpClient, url, "POST");
    }

    public static MyHttpRequest put(HttpClient httpClient, String url) {
        return new MyHttpRequest(httpClient, url, "PUT");
    }

}